Skip to content

feat: validate-agents cobre squads e reporta YAML inválido em vez de engolir - #816

Open
GiovaneLaurencio wants to merge 3 commits into
SynkraAI:mainfrom
GiovaneLaurencio:feat/validate-agents-cobre-squads
Open

feat: validate-agents cobre squads e reporta YAML inválido em vez de engolir#816
GiovaneLaurencio wants to merge 3 commits into
SynkraAI:mainfrom
GiovaneLaurencio:feat/validate-agents-cobre-squads

Conversation

@GiovaneLaurencio

@GiovaneLaurencio GiovaneLaurencio commented Jul 27, 2026

Copy link
Copy Markdown

📋 Description

Follow-up to #815, which surfaced the gap: npm run validate:agents validated only the 12 core agents. Squads had no gate at all — that is why four typographic variants of the same guard block and a YAML block no parser accepts coexisted in squads/claude-code-mastery/ unnoticed.

While extending it, a worse problem showed up in the loader itself:

try {
  const files = await fs.readdir(AGENTS_DIR);
  for (const file of files) { /* ... yaml.load may throw ... */ }
} catch (error) {
  console.error(`Error reading agents directory: ${error.message}`);
}

The try/catch wraps the entire loop. One unparseable agent aborted the read of every remaining agent, the failure surfaced as a console.error, and the run still ended with ✅ All validations passed! and exit 0. The gate existed on paper only.

📦 Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🐛 Bug fix (the loader swallowing parse failures)

🎯 Scope

  • Core framework (aiox-core/)

📝 Changes Made

Squad coverage. Discovers squads/<squad>/agents/ alongside the core directory, with a per-scope summary:

  Agents validated: 20
    • core: 12
    • squad:claude-code-mastery: 8

New "YAML Parse" check. An unparseable block is now an error (exit 1) with the reason and a fix hint, and the try/catch is per file — one broken agent no longer hides the others.

Command uniqueness is per scope. Core competes with core, each squad with itself. The same command name in two different squads is not a collision — without this, enabling squads would have produced mass false positives (the squad alone has 97 commands).

Squad dependencies resolve inside the squad (squads/<squad>/<type>/), and reference_files — which hold repo-relative paths — are checked against the repo root instead of being reported as UNKNOWN_DEP_TYPE.

--core-only restricts the run to the previous behaviour.

Also removed a dead function the refactor left behind referencing undefined variables.

loadAllAgents() keeps its old signature (returns just the list) so existing consumers don't break; loadAgents() is the new one, returning { agents, parseErrors, scopes }.

🧪 Testing

  • Current repo: 0 errors, 121 warnings — all pre-existing missing checklists/+data/ dependencies in core, none introduced here.
  • --core-only → 12 agents, matching the old output.
  • Gate verified with a deliberately broken agent dropped into the squad:
🔍 YAML Parse Check
  ❌ Unparseable YAML in squad:claude-code-mastery/zz-teste-quebrado.md: bad indentation of a sequence entry (6:13)
...
  Agents validated: 20    ← the other 20 still validated
  Errors: 1
exit=1                    ← would block CI

Removing the file returns exit 0. Files starting with _ remain skipped by design.

  • npm run lint passes.

🔗 Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Agent validation now covers both core and squad agents, with scoped command checks and squad-aware dependency resolution.
    • Added an option to validate core agents only.
    • Validation reports YAML parsing errors clearly and includes per-scope agent counts.
  • Bug Fixes

    • Improved guidance when activation occurs outside a project or without a detected Git repository.
    • Added clearer recommendations based on the current working directory.
  • Documentation

    • Expanded validation documentation covering squad discovery, dependencies, parsing errors, and available options.

GiovaneLaurencio and others added 3 commits July 26, 2026 18:50
…ents

The squad's 8 agents carried the same GREENFIELD GUARD as the core agents, with
the same two defects plus a consistency problem of their own.

Agent Authority (Constitution Article II): 4 of the 8 recommended
`*environment-bootstrap`, a @devops-exclusive command none of them owns
(hooks-architect, mcp-integrator, skill-craftsman, swarm-orchestrator). They now
delegate: `@devops *environment-bootstrap`. The 3 agents that recommend a
command they actually own keep it unchanged — `*configure` (config-engineer),
`*integrate-project` (project-integrator), `*check-updates` (roadmap-sentinel);
each was verified to exist in its own `commands` block.

False positive in non-project directories: the guard fired wherever there was no
git repo, including a user home directory, and labelled it a "Greenfield
project". It now decides from the working directory path already present in the
system prompt (zero extra I/O): a home or non-project directory gets a tip to
activate from inside the project instead of a command recommendation.
claude-mastery-chief, which never recommended anything, gets the tip branch only.

Typographic consistency: the three guard lines existed in 4 different variants
across the 8 files (`--` vs `—`, with and without bold, with and without emoji).
All 8 now carry byte-identical lines, matching the core agents.

Note: `npm run validate:agents` covers only the 12 core agents, not squads. A
manual YAML parse of all 8 found swarm-orchestrator.md invalid at its `lexicon:`
block — pre-existing (fails identically at HEAD, before this change) and left
untouched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 8 entries under `voice_dna.lexicon` had the shape:

    - "topology" (preferred over "structure" for agent arrangements)

YAML closes the scalar at the second quote, leaving ` (preferred ...)` as a
stray token — js-yaml rejected the whole embedded block with "bad indentation
of a sequence entry", so the agent definition was unparseable by any tooling.

Each entry is now wrapped in single quotes, which keeps the inner double quotes
verbatim and keeps the parenthetical part of the value rather than turning it
into a comment:

    - '"topology" (preferred over "structure" for agent arrangements)'

Verified: all 8 squad agents now parse, and lexicon still yields 8 entries with
their text unchanged. Entries elsewhere in the squad of the form
`- "text" # comment` were left alone — the `#` makes those valid already.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…engolir

`npm run validate:agents` validava só os 12 agentes core. Squads não tinham gate
nenhum — foi por isso que 4 variantes tipográficas do mesmo guard e um bloco YAML que
nenhum parser aceita conviveram sem ninguém notar (ver SynkraAI#815).

Pior: o loader tinha um try/catch em volta do loop INTEIRO. Um YAML quebrado abortava
a leitura de todos os agentes seguintes, imprimia a falha como console.error e o run
ainda terminava com "All validations passed". O gate existia no papel.

O que muda:

- descobre `squads/<squad>/agents/` além do core. Sumário por escopo:
  20 agentes = core 12 + squad:claude-code-mastery 8.
- NOVO check "YAML Parse": bloco que não parseia é ERRO (exit 1) com o motivo e uma
  sugestão. try/catch agora é POR ARQUIVO — um quebrado não esconde os outros.
  Verificado com um agente propositalmente inválido: acusou, saiu 1, e seguiu
  validando os 20.
- unicidade de comando passa a ser POR ESCOPO. Core disputa com core, cada squad com
  ele mesmo — mesmo comando em dois squads diferentes não é colisão. Sem isso, ligar
  squads geraria falso-positivo em massa.
- dependências de squad resolvem em `squads/<squad>/<type>/`; `reference_files` (que
  guardam caminho relativo à raiz) passam a ser checados contra o repo em vez de virar
  "unknown dependency type".
- `--core-only` restringe ao comportamento antigo.
- removida uma função morta que o refactor deixou referenciando variáveis inexistentes.

`loadAllAgents()` mantém a assinatura antiga (só a lista) para não quebrar consumidor;
`loadAgents()` é o novo, com parseErrors e escopos.

Estado atual do repo: 0 erros, 121 warnings — todas dependências ausentes
pré-existentes no core, nenhuma introduzida aqui.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@GiovaneLaurencio is attempting to deploy a commit to the SINKRA - AIOX Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The agent validator now discovers and validates core and squad agents with scoped command and dependency rules, YAML parse reporting, and a --core-only option. Related task documentation and installation metadata were refreshed. Several squad activation instructions now provide conditional no-Git guidance.

Changes

Scoped agent validation

Layer / File(s) Summary
Scoped loading and validation
.aiox-core/infrastructure/scripts/validate-agents.js
Agent discovery, YAML parsing, command checks, dependency resolution, reporting, CLI handling, and exports now support core and squad scopes.
Validation task documentation
.aiox-core/development/tasks/validate-agents.md
Documentation covers squad discovery, scoped rules, YAML errors, local dependencies, skipped files, and --core-only.
Installation manifest refresh
.aiox-core/install-manifest.yaml
Manifest timestamps, hashes, and sizes are refreshed for validation, template, hook, and product files.

Greenfield activation guidance

Layer / File(s) Summary
Conditional no-Git activation guidance
squads/claude-code-mastery/agents/*.md
Activation messages now distinguish home or non-project directories from project directories when no Git repository is detected.
Lexicon formatting
squads/claude-code-mastery/agents/swarm-orchestrator.md
Lexicon entries are reformatted onto separate lines without changing their terms.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant AgentLoader
  participant Validators
  participant ResultFormatter
  CLI->>AgentLoader: Load core and optional squad agents
  AgentLoader->>Validators: Provide agents and YAML parse errors
  Validators->>Validators: Check scoped commands and dependencies
  Validators->>ResultFormatter: Provide validation results and scope counts
  ResultFormatter->>CLI: Display errors, warnings, and summary
Loading

Possibly related PRs

Suggested labels: area: agents, area: core

Suggested reviewers: oalanicolas

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: squad validation plus explicit YAML error reporting instead of silently swallowing failures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/validate-agents-cobre-squads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (ai_padded_prose). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.aiox-core/infrastructure/scripts/validate-agents.js (1)

642-669: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

--help output doesn't mention the new --core-only flag.

The top-of-file JSDoc usage block (lines 21-23) and CLI parsing (line 664) both support --core-only, but the interactive --help/-h text still only lists --json and --fix-suggestions. Users running --help won't discover the new flag.

📝 Suggested fix
 Usage:
-  node validate-agents.js                    Validate all agents
+  node validate-agents.js                    Validate core + squad agents
+  node validate-agents.js --core-only        Validate only the 12 core agents
   node validate-agents.js --json             Output as JSON
   node validate-agents.js --fix-suggestions  Show fix suggestions
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/infrastructure/scripts/validate-agents.js around lines 642 - 669,
Update the help text in main() to include the supported --core-only option
alongside the existing CLI usage entries, matching the flag already parsed in
the options object and documented by the top-level JSDoc.
.aiox-core/development/tasks/validate-agents.md (1)

72-81: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Step 5 wasn't updated for squad-local dependency resolution.

Step 1 and the Dependencies section now document that squad agents resolve tasks/checklists against squads/<squad>/<type>/, but Step 5 still only mentions the core .aiox-core/development/... directories, leaving the dependency-validation spec internally inconsistent.

📝 Suggested fix
 For each agent's `dependencies.tasks` list:
-1. Check that each referenced task file exists in `.aiox-core/development/tasks/`
+1. Check that each referenced task file exists in `.aiox-core/development/tasks/`
+   (or `squads/<squad>/tasks/` for squad-scoped agents)
 2. Report missing dependencies as ERRORS
 
 For each agent's `dependencies.checklists` list:
-1. Check in `.aiox-core/development/checklists/`
+1. Check in `.aiox-core/development/checklists/`
+   (or `squads/<squad>/checklists/` for squad-scoped agents)
 2. Report missing as WARNINGS

As per path instructions, "Validate any referenced dependencies exist in the codebase" for .aiox-core/development/tasks/**, and this dependency-resolution documentation should stay consistent with the rest of the task spec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/development/tasks/validate-agents.md around lines 72 - 81, Update
Step 5 in the dependency-validation instructions to resolve each agent’s task
and checklist references using the squad-local directories documented elsewhere,
while retaining `.aiox-core/development/tasks/` and
`.aiox-core/development/checklists/` as applicable core locations. Keep missing
tasks as ERRORS and missing checklists as WARNINGS, and make the validation
paths consistent with the task’s existing dependency-resolution rules.

Source: Path instructions

🧹 Nitpick comments (1)
.aiox-core/infrastructure/scripts/validate-agents.js (1)

677-684: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Portuguese-language comment amid an English codebase.

The comment above loadAllAgents is in Portuguese while the rest of the file's comments are English, which could hinder readability for non-Portuguese-speaking maintainers.

♻️ Suggested fix
-  // loadAllAgents mantém a assinatura antiga (devolve só a lista) para não quebrar
-  // quem já consome; loadAgents é o novo, com parseErrors e escopos.
+  // loadAllAgents keeps its original signature (returns only the list) to avoid
+  // breaking existing consumers; loadAgents is the new API, with parseErrors and scopes.
   loadAllAgents,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/infrastructure/scripts/validate-agents.js around lines 677 - 684,
Translate the comment above the exported loadAllAgents and loadAgents symbols
from Portuguese into clear, concise English, preserving its explanation that
loadAllAgents retains the legacy list-only signature while loadAgents adds
parseErrors and scopes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.aiox-core/infrastructure/scripts/validate-agents.js:
- Around line 121-168: The loadAgentsFromDir function should distinguish file
read failures from YAML parsing failures. Handle fs.readFile errors separately
with an appropriate read-error type and message, then keep
extractYamlFromMarkdown errors classified as INVALID_YAML with the existing YAML
suggestion.
- Around line 235-249: Update commandName to detect explicit-format objects
using Object.hasOwn(cmd, 'name'), including empty names, and return cmd.name ||
undefined. Prevent malformed explicit commands from reaching the shorthand
Object.keys branch and producing a bogus command name.
- Around line 325-343: Constrain `reference_files` resolution in the `depType
=== 'reference_files'` block so entries containing traversal cannot escape
`REPO_ROOT`. Resolve the path, validate that it remains within `REPO_ROOT`, and
only call `fileExists` or expose `expectedPath` for contained paths; handle
escaping entries as missing or invalid references using the existing warning
flow.

---

Outside diff comments:
In @.aiox-core/development/tasks/validate-agents.md:
- Around line 72-81: Update Step 5 in the dependency-validation instructions to
resolve each agent’s task and checklist references using the squad-local
directories documented elsewhere, while retaining
`.aiox-core/development/tasks/` and `.aiox-core/development/checklists/` as
applicable core locations. Keep missing tasks as ERRORS and missing checklists
as WARNINGS, and make the validation paths consistent with the task’s existing
dependency-resolution rules.

In @.aiox-core/infrastructure/scripts/validate-agents.js:
- Around line 642-669: Update the help text in main() to include the supported
--core-only option alongside the existing CLI usage entries, matching the flag
already parsed in the options object and documented by the top-level JSDoc.

---

Nitpick comments:
In @.aiox-core/infrastructure/scripts/validate-agents.js:
- Around line 677-684: Translate the comment above the exported loadAllAgents
and loadAgents symbols from Portuguese into clear, concise English, preserving
its explanation that loadAllAgents retains the legacy list-only signature while
loadAgents adds parseErrors and scopes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fd51256f-2228-491d-9e21-6e70ff8abb2c

📥 Commits

Reviewing files that changed from the base of the PR and between 0b32b68 and 04a05a8.

📒 Files selected for processing (11)
  • .aiox-core/development/tasks/validate-agents.md
  • .aiox-core/infrastructure/scripts/validate-agents.js
  • .aiox-core/install-manifest.yaml
  • squads/claude-code-mastery/agents/claude-mastery-chief.md
  • squads/claude-code-mastery/agents/config-engineer.md
  • squads/claude-code-mastery/agents/hooks-architect.md
  • squads/claude-code-mastery/agents/mcp-integrator.md
  • squads/claude-code-mastery/agents/project-integrator.md
  • squads/claude-code-mastery/agents/roadmap-sentinel.md
  • squads/claude-code-mastery/agents/skill-craftsman.md
  • squads/claude-code-mastery/agents/swarm-orchestrator.md

Comment on lines +121 to +168
async function loadAgentsFromDir(dir, scope, depDirs) {
const agents = [];
const parseErrors = [];

let files;
try {
const files = await fs.readdir(AGENTS_DIR);
for (const file of files) {
if (file.endsWith('.md') && !file.startsWith('_')) {
const filePath = path.join(AGENTS_DIR, file);
const content = await fs.readFile(filePath, 'utf-8');
const parsed = extractYamlFromMarkdown(content);

if (parsed?.agent) {
agents.push({
file,
path: filePath,
id: parsed.agent.id || file.replace('.md', ''),
name: parsed.agent.name,
commands: parsed.commands || [],
dependencies: parsed.dependencies || {},
parsed,
});
}
files = await fs.readdir(dir);
} catch {
return { agents, parseErrors }; // directory absent — nothing to validate
}

for (const file of files) {
if (!file.endsWith('.md') || file.startsWith('_')) continue;

const filePath = path.join(dir, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const parsed = extractYamlFromMarkdown(content);

if (parsed?.agent) {
agents.push({
file,
path: filePath,
scope,
depDirs,
id: parsed.agent.id || file.replace('.md', ''),
name: parsed.agent.name,
commands: parsed.commands || [],
dependencies: parsed.dependencies || {},
parsed,
});
}
} catch (error) {
parseErrors.push({
type: 'INVALID_YAML',
scope,
agent: file.replace('.md', ''),
file,
path: filePath,
message: `Unparseable YAML in ${scope}/${file}: ${String(error.message).split('\n')[0]}`,
suggestion:
'Fix the embedded YAML block. A common cause is a sequence entry like `- "term" (note)`, where the text after the closing quote is a stray token — wrap the whole entry in single quotes.',
});
}
} catch (error) {
console.error(`Error reading agents directory: ${error.message}`);
}

return { agents, parseErrors };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Read failures are mislabeled as YAML parse errors.

The try/catch wraps both fs.readFile and extractYamlFromMarkdown. If the read itself fails (permissions, file removed mid-scan), the catch still reports INVALID_YAML with a YAML-fixing suggestion, which misleads whoever investigates the report.

🐛 Suggested fix
     } catch (error) {
       parseErrors.push({
         type: 'INVALID_YAML',
         scope,
         agent: file.replace('.md', ''),
         file,
         path: filePath,
-        message: `Unparseable YAML in ${scope}/${file}: ${String(error.message).split('\n')[0]}`,
+        message: `Failed to load ${scope}/${file}: ${String(error.message).split('\n')[0]}`,
         suggestion:
           'Fix the embedded YAML block. A common cause is a sequence entry like `- "term" (note)`, where the text after the closing quote is a stray token — wrap the whole entry in single quotes.',
       });
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function loadAgentsFromDir(dir, scope, depDirs) {
const agents = [];
const parseErrors = [];
let files;
try {
const files = await fs.readdir(AGENTS_DIR);
for (const file of files) {
if (file.endsWith('.md') && !file.startsWith('_')) {
const filePath = path.join(AGENTS_DIR, file);
const content = await fs.readFile(filePath, 'utf-8');
const parsed = extractYamlFromMarkdown(content);
if (parsed?.agent) {
agents.push({
file,
path: filePath,
id: parsed.agent.id || file.replace('.md', ''),
name: parsed.agent.name,
commands: parsed.commands || [],
dependencies: parsed.dependencies || {},
parsed,
});
}
files = await fs.readdir(dir);
} catch {
return { agents, parseErrors }; // directory absent — nothing to validate
}
for (const file of files) {
if (!file.endsWith('.md') || file.startsWith('_')) continue;
const filePath = path.join(dir, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const parsed = extractYamlFromMarkdown(content);
if (parsed?.agent) {
agents.push({
file,
path: filePath,
scope,
depDirs,
id: parsed.agent.id || file.replace('.md', ''),
name: parsed.agent.name,
commands: parsed.commands || [],
dependencies: parsed.dependencies || {},
parsed,
});
}
} catch (error) {
parseErrors.push({
type: 'INVALID_YAML',
scope,
agent: file.replace('.md', ''),
file,
path: filePath,
message: `Unparseable YAML in ${scope}/${file}: ${String(error.message).split('\n')[0]}`,
suggestion:
'Fix the embedded YAML block. A common cause is a sequence entry like `- "term" (note)`, where the text after the closing quote is a stray token — wrap the whole entry in single quotes.',
});
}
} catch (error) {
console.error(`Error reading agents directory: ${error.message}`);
}
return { agents, parseErrors };
}
async function loadAgentsFromDir(dir, scope, depDirs) {
const agents = [];
const parseErrors = [];
let files;
try {
files = await fs.readdir(dir);
} catch {
return { agents, parseErrors }; // directory absent — nothing to validate
}
for (const file of files) {
if (!file.endsWith('.md') || file.startsWith('_')) continue;
const filePath = path.join(dir, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const parsed = extractYamlFromMarkdown(content);
if (parsed?.agent) {
agents.push({
file,
path: filePath,
scope,
depDirs,
id: parsed.agent.id || file.replace('.md', ''),
name: parsed.agent.name,
commands: parsed.commands || [],
dependencies: parsed.dependencies || {},
parsed,
});
}
} catch (error) {
parseErrors.push({
type: 'INVALID_YAML',
scope,
agent: file.replace('.md', ''),
file,
path: filePath,
message: `Failed to load ${scope}/${file}: ${String(error.message).split('\n')[0]}`,
suggestion:
'Fix the embedded YAML block. A common cause is a sequence entry like `- "term" (note)`, where the text after the closing quote is a stray token — wrap the whole entry in single quotes.',
});
}
}
return { agents, parseErrors };
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 136-136: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFile(filePath, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/infrastructure/scripts/validate-agents.js around lines 121 - 168,
The loadAgentsFromDir function should distinguish file read failures from YAML
parsing failures. Handle fs.readFile errors separately with an appropriate
read-error type and message, then keep extractYamlFromMarkdown errors classified
as INVALID_YAML with the existing YAML suggestion.

Comment on lines +235 to +249
function commandName(cmd) {
if (typeof cmd === 'string') {
// String format: 'command: description'
return cmd.split(':')[0].trim();
}
if (cmd && cmd.name) {
// Explicit format: { name: 'command', ... }
return cmd.name;
}
if (typeof cmd === 'object' && cmd !== null) {
// Shorthand format: { command: 'description' } - take first key
return Object.keys(cmd)[0]?.split(' ')[0]; // Handle 'command {args}' format
}
return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target=".aiox-core/infrastructure/scripts/validate-agents.js"
if [ -f "$target" ]; then
  echo "FILE_EXISTS: yes"
  wc -l "$target"
  printf '\n--- lines 200-270 ---\n'
  sed -n '200,270p' "$target" | nl -ba -v200
  printf '\n--- commandName usages/callers nearby ---\n'
  rg -n "commandName\\(|DUPLICATE_COMMAND|validate.*agent|agents" "$target" -C 3
else
  echo "FILE_EXISTS: no"
  echo "Matching files:"
  fd -a "validate-agents.js" . | sed 's#^\./##'
fi

Repository: SynkraAI/aiox-core

Length of output: 293


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target=".aiox-core/infrastructure/scripts/validate-agents.js"

echo "--- lines 200-270 ---"
sed -n '200,270p' "$target" | awk '{printf "%6d  %s\n", NR+199, $0}'

echo
echo "--- relevant searches ---"
for term in "commandName\\(" "DUPLICATE_COMMAND" "validate.*agent"; do
  echo "=== $term ==="
  grep -En "$term" "$target" -C 2 || true
done

echo
echo "--- minimal JS lexical probe in a temp .js file without executing repo code ---"
tmp="$(mktemp)"
sed -n '1,16p' "$target"
# Since Node/JS runtime may not be available and even if it is, running package code is discouraged,
# inspect the helper implementation via pure shell text for the current change.

Repository: SynkraAI/aiox-core

Length of output: 5700


🌐 Web query:

Node.js Object.prototype.hasOwnProperty.call documentation

💡 Result:

In Node.js and other JavaScript environments, Object.prototype.hasOwnProperty.call(obj, prop) is a defensive, reliable way to check if an object has a specific property as its own (non-inherited) property [1][2][3]. While you can often use obj.hasOwnProperty(prop), using the generic method call is recommended to avoid several potential pitfalls [4][5]: 1. Shadowed Properties: If an object has a custom property named hasOwnProperty, calling obj.hasOwnProperty will execute that property instead of the built-in method, potentially causing errors or security vulnerabilities [5][3]. This is particularly risky when dealing with untrusted input (e.g., JSON objects from a web request) [5]. 2. Objects Lacking Prototype: Objects created with Object.create(null) do not inherit from Object.prototype, meaning they do not possess the hasOwnProperty method at all [1][2][5]. Calling obj.hasOwnProperty on these objects will throw a TypeError [2][6][3]. 3. Method Overriding: If the hasOwnProperty method has been redefined or deleted on the object's prototype chain, the standard method call may fail or behave unpredictably [4][3]. Using Object.prototype.hasOwnProperty.call(obj, prop) bypasses these issues by directly invoking the native method from Object.prototype and explicitly setting the 'this' context to the target object [1][3]. Modern Recommendation: In modern JavaScript environments (including recent versions of Node.js), it is recommended to use Object.hasOwn(obj, prop) instead [1][7][6]. This method is cleaner, more readable, and designed specifically to be a safer, built-in replacement for the older, verbose pattern [1][6]. Example: const obj = { key: 'value' }; // Recommended modern approach Object.hasOwn(obj, 'key'); // true // Robust older pattern Object.prototype.hasOwnProperty.call(obj, 'key'); // true // Risky approach (avoid if the object source is untrusted) obj.hasOwnProperty('key'); // true (but unsafe)

Citations:


Do not fall through malformed explicit-format commands to the shorthand branch.

A command like { name: '', description: '...' } skips the cmd && cmd.name check and is treated as shorthand, returning the literal key 'name'; different malformed commands can therefore collide under the same bogus command. Guard the explicit format with Object.hasOwn(cmd, 'name') and return cmd.name || undefined so it fails with a missing name instead of creating a duplicate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/infrastructure/scripts/validate-agents.js around lines 235 - 249,
Update commandName to detect explicit-format objects using Object.hasOwn(cmd,
'name'), including empty names, and return cmd.name || undefined. Prevent
malformed explicit commands from reaching the shorthand Object.keys branch and
producing a bogus command name.

Comment on lines +325 to +343
if (depType === 'reference_files') {
for (const refFile of depList) {
if (typeof refFile !== 'string') continue;
const refPath = path.join(REPO_ROOT, refFile);
if (!(await fileExists(refPath))) {
warnings.push({
type: 'MISSING_REFERENCE_FILE',
scope: agent.scope,
agent: agent.id,
depType,
depFile: refFile,
expectedPath: refPath,
message: `Missing reference file: @${agent.id} → ${refFile}`,
suggestion: `Create ${refFile} or remove it from the agent's reference_files.`,
});
}
}
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

reference_files entries can escape REPO_ROOT via ../ traversal.

Unlike file-based deps resolved against a fixed <type> directory, refFile is joined directly onto REPO_ROOT with no containment check. A crafted reference_files entry (e.g. ../../etc/passwd) would have its existence probed outside the repo and the resolved path echoed back in the warning's expectedPath. Given squads can be sourced externally (squad-downloader), it's worth constraining the resolved path to stay under REPO_ROOT.

🛡️ Suggested fix
       if (depType === 'reference_files') {
         for (const refFile of depList) {
           if (typeof refFile !== 'string') continue;
           const refPath = path.join(REPO_ROOT, refFile);
+          if (!refPath.startsWith(REPO_ROOT + path.sep)) {
+            continue; // reject paths that escape the repository root
+          }
           if (!(await fileExists(refPath))) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (depType === 'reference_files') {
for (const refFile of depList) {
if (typeof refFile !== 'string') continue;
const refPath = path.join(REPO_ROOT, refFile);
if (!(await fileExists(refPath))) {
warnings.push({
type: 'MISSING_REFERENCE_FILE',
scope: agent.scope,
agent: agent.id,
depType,
depFile: refFile,
expectedPath: refPath,
message: `Missing reference file: @${agent.id}${refFile}`,
suggestion: `Create ${refFile} or remove it from the agent's reference_files.`,
});
}
}
continue;
}
if (depType === 'reference_files') {
for (const refFile of depList) {
if (typeof refFile !== 'string') continue;
const refPath = path.join(REPO_ROOT, refFile);
if (!refPath.startsWith(REPO_ROOT + path.sep)) {
continue; // reject paths that escape the repository root
}
if (!(await fileExists(refPath))) {
warnings.push({
type: 'MISSING_REFERENCE_FILE',
scope: agent.scope,
agent: agent.id,
depType,
depFile: refFile,
expectedPath: refPath,
message: `Missing reference file: @${agent.id}${refFile}`,
suggestion: `Create ${refFile} or remove it from the agent's reference_files.`,
});
}
}
continue;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/infrastructure/scripts/validate-agents.js around lines 325 - 343,
Constrain `reference_files` resolution in the `depType === 'reference_files'`
block so entries containing traversal cannot escape `REPO_ROOT`. Resolve the
path, validate that it remains within `REPO_ROOT`, and only call `fileExists` or
expose `expectedPath` for contained paths; handle escaping entries as missing or
invalid references using the existing warning flow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant